[Lang] 정수형 크기(uint16_t)와 비트·바이트 메모리 저장 원리
핵심 질문:
uint16_t msg_type은 2바이트인데, 어떻게 1001이라는 숫자를 담을 수 있는가?
“2바이트니까 10진수 2자리(0~99)만 들어가는 것 아닌가?” 또는 “1001은 4글자니까 4바이트가 필요한 게 아닌가?”라는 흔한 의문을 완벽히 해소한다.
1. 2바이트(uint16_t)의 실제 표현 범위
컴퓨터의 메모리 단위인 **바이트(Byte)**는 10진수 자릿수가 아니라 **8개의 2진수 비트(Bit)**를 묶은 단위이다.
- 1비트(Bit): 0 또는 1 (2가지 상태)
- 1바이트(Byte) = 8비트 가지 ()
- 2바이트(uint16_t) = 16비트 가지 ()
1 Byte (8 bit) 1 Byte (8 bit)
+-------------------------+-------------------------+
| b15 b14 ... b8 | b7 b6 ... b0 | = 총 16 bit (uint16_t)
+-------------------------+-------------------------+
MSB (최상위 비트) LSB (최하위 비트)IMPORTANT
uint16_t는 부터 까지의 모든 정수를 담을 수 있다.
따라서 1001은 최대치(65,535)의 약 1.5%에 불과한 작은 숫자이므로 2바이트 안에 아주 여유롭게 저장된다.
2. 1001이 메모리에 저장되는 실제 이진수 구조
10진수 1001을 16진수와 2진수로 변환하면 다음과 같다.
- 10진수:
1001 - 16진수:
0x03E9(16진수 1자리는 4비트, 2자리가 1바이트) - 2진수 (16비트):
0000 0011 1110 1001
[ 상위 1바이트 (High Byte) ] [ 하위 1바이트 (Low Byte) ]
비트: 0 0 0 0 0 0 1 1 1 1 1 0 1 0 0 1
16진수: 0x03 0xE9메모리 바이트 배치 (엔디안: Endianness)
이 2바이트가 RAM에 어떻게 배치되는지는 CPU 아키텍처에 따라 결정된다:
- 빅 엔디안 (Big-Endian) - 네트워크 바이트 순서(Network Byte Order):
- 상위 바이트부터 순서대로 저장:
[ 0x03 ] [ 0xE9 ]
- 상위 바이트부터 순서대로 저장:
- 리틀 엔디안 (Little-Endian) - x86 / AMD64 (일반적인 인텔 리눅스 서버):
- 하위 바이트부터 역순으로 저장:
[ 0xE9 ] [ 0x03 ]
- 하위 바이트부터 역순으로 저장:
네트워크 통신에서
htons()를 쓰는 이유호스트 CPU가 리틀 엔디안(
0xE9 0x03)이라도, 네트워크 선로로 전송할 때는 표준 규격인 빅 엔디안(0x03 0xE9)으로 맞춰서 보내야 상대방이 올바르게 1001로 읽을 수 있다.
그래서htons(1001)(Host to Network Short) 함수를 호출하는 것이다.
3. 흔히 하는 오해 분석: 문자열 vs 정수형
오해 1: “2바이트니까 10진수 2자리(0~99)만 들어가는 것 아닌가?”
- 원인: 사람이 쓰는 10진수 자릿수 개념과 컴퓨터의 2진수 바이트 개념을 혼동함.
- 진실: 바이트는 10진수 자릿수가 아니라 진법 체계이므로, 2바이트는 가지 수를 나타냄.
오해 2: “1001은 4글자니까 4바이트가 필요한 것 아닌가?”
- 원인: 문자(Character) 저장 방식과 숫자(Integer) 저장 방식의 차이를 인지하지 못함.
| 비교 항목 | 문자열 저장 (char msg[5] = "1001") | 정수형 저장 (uint16_t msg = 1001) |
|---|---|---|
| 저장 형태 | 각 글자의 ASCII 코드값과 널문자(\0) | 숫자 자체를 2진수 비트(0x03E9)로 저장 |
| 메모리 구성 | '1'(0x31) | '0'(0x30) | '0'(0x30) | '1'(0x31) | '\0'(0x00) | 0x03 | 0xE9 |
| 필요 용량 | 최소 5 바이트 | 단 2 바이트 |
| 통신 효율 | 텍스트 파싱(atoi) 필요, 오버헤드 큼 | CPU 레지스터에 즉시 로드, 처리 속도 극대화 |
통신 프로토콜(패킷 헤더)에서
msg_type을 문자열"1001"이 아니라uint16_t숫자로 정의하는 이유가 바로 여기에 있다. 단 2바이트만으로 65,535가지의 메시지 종류를 완벽히 구분할 수 있기 때문이다.
4. C99 표준 고정 크기 정수형 (stdint.h) 정리
시스템 프로그래밍 및 네트워크 패킷 헤더 설계 시 플랫폼(32비트/64비트)에 상관없이 크기를 보장하기 위해 <stdint.h>의 타입을 사용한다.
| C99 타입 | 크기 | 부호 | 표현 범위 | 대표 활용 사례 |
|---|---|---|---|---|
uint8_t | 1 Byte (8 bit) | Unsigned | IPv4 주소 각 자리, 플래그 비트 | |
int8_t | 1 Byte (8 bit) | Signed | 작은 오프셋, 상대 증감값 | |
uint16_t | 2 Byte (16 bit) | Unsigned | TCP/UDP 포트 번호, 패킷 길이, 메시지 타입(msg_type) | |
int16_t | 2 Byte (16 bit) | Signed | 센서 데이터, 오디오 샘플 | |
uint32_t | 4 Byte (32 bit) | Unsigned | (약 42억) | PCODE(0x01010101), 시퀀스 번호(seq), IPv4 주소(통합 정수) |
int32_t | 4 Byte (32 bit) | Signed | 일반 정수 연산 | |
uint64_t | 8 Byte (64 bit) | Unsigned | (약 1800경) | 밀리초/마이크로초 타임스탬프, 누적 트래픽 바이트 수 |
int64_t | 8 Byte (64 bit) | Signed | 대용량 DB PK, Unix Time |
5. 핵심 요약
- **2바이트(
uint16_t)**는 16개의 0과 1로 숫자를 나타내므로 최대 65,535까지 표현 가능하다. - 따라서 1001은 2바이트(
0x03E9) 안에 매우 여유롭게 들어간다. - 숫자를 문자열로 저장하면 글자당 1바이트씩 들어서 낭비되지만, 정수형 바이너리로 저장하면 2바이트만으로도 6만 가지 이상의 상태/타입을 효율적으로 표현할 수 있다.